/*
Code : Dzlua
Email: 505544956@qq.com
Time : 2017/06/11
*/
#ifndef DZLUA_LIBEVENT_TCP_SERVER_H_
#define DZLUA_LIBEVENT_TCP_SERVER_H_

#pragma once

#include <event.h>
#include <event2/listener.h>
#include <event2/thread.h>

#include <memory>
#include <thread>
#include <vector>

#if defined(WIN32) || defined(_WIN32)
#   define DZLUA_OS_WINDOWS 1
#endif

#include <iostream>
using std::cout;
using std::endl;

namespace dzlua {

namespace cs {

class Server;

class Conn {
public:
    Conn() {
        cout << "new cs::coon" << endl;
    }
    ~Conn() {
        cout << "delete cs::coon" << endl;
    }
};

class Thread {
    friend class Server;
private:
    Thread() : m_td(nullptr), m_base(nullptr) {
        m_conn.clear();
        cout << "new cs::thread" << endl;
    }
public:
    ~Thread() {
        m_conn.clear();

        if (m_base) {
            event_base_free(m_base);
            m_base = nullptr;
        }

        cout << "delete cs::thread" << endl;
    }

private:
    bool Init(std::shared_ptr<Server> server) {
        m_conn.clear();
        m_server = server;

        m_base = event_base_new();
        if (!m_base) return false;

#if defined(DZLUA_OS_WINDOWS)
        evutil_socket_t fds[2];
        if(evutil_socketpair(AF_INET, SOCK_STREAM, 0, fds) < 0)
            return false;
        evutil_make_socket_nonblocking(fds[0]);
        evutil_make_socket_nonblocking(fds[1]);
#else
        int fds[2];
        if (pipe(fds) < 0) return false;
#endif

        m_recv = fds[0];
        m_send = fds[1];

        event_set( &m_notify, m_recv,
                EV_READ | EV_PERSIST, &Thread::ThreadProcess, this);
        if ( event_base_set(m_base, &m_notify) < 0
                || event_add(&m_notify, nullptr) < 0 ) {
            evutil_closesocket(fds[0]);
            evutil_closesocket(fds[1]);
            return false;
        }
        return true;
    }

    void Start() {
        m_td.reset(new std::thread(
                    [] () {
                        cout << "thread" << endl;
                    }));
    }
private:
    static void ThreadProcess(int fd, short which, void *arg) {
        Thread* ltd = (Thread*)arg;

        evutil_socket_t confd;
	    recv(ltd->m_recv, (char*)&confd, sizeof(evutil_socket_t), 0);

        if (confd == -1) {
            event_base_loopbreak(ltd->m_base);
            return;
        }

        struct bufferevent *bev = nullptr;
        bev = bufferevent_socket_new(ltd->m_base, confd, BEV_OPT_CLOSE_ON_FREE);
        if (!bev) {
            event_base_loopbreak(ltd->m_base);
            return;
        }

        std::shared_ptr<Conn> conn(new Conn(confd)); 
        bufferevent_setcb(bev,
            &MultiServer::ReadEventCb,
            &MultiServer::WriteEventCb,
            &MultiServer::CloseEventCb, conn);
        bufferevent_enable(bev, EV_WRITE);
        bufferevent_enable(bev, EV_READ);
    }
private:
    std::shared_ptr<std::thread> m_td;
    std::weak_ptr<Server> m_server;
    std::vector<std::shared_ptr<Conn>> m_conn;
    struct event_base *m_base;
    struct event m_notify;
    evutil_socket_t m_recv;
    evutil_socket_t m_send;
};

class Server
    : public std::enable_shared_from_this<Server> {
public:
    static std::shared_ptr<Server> NewServer() {
        return std::shared_ptr<Server>(new Server);
    }

    bool Init(int tds, int port) {
#if defined(DZLUA_OS_WINDOWS)
        {
            WORD wVersion;
            WSADATA wsaData;
            wVersion = MAKEWORD(2, 2);
            int err = WSAStartup(wVersion, &wsaData);
            if (0 != err) return false;

            if (wsaData.wVersion != wVersion) {
                return false;
            }
        }
        evthread_use_windows_threads();
#else
        evthread_use_pthreads();
#endif
        m_port = port;
        m_base = event_base_new();

        if (!m_base || m_port < 0)
            return false;

        m_threads.clear();
        for (int i = 0; i < tds; ++i) {
            std::shared_ptr<Thread> td(new Thread);
            if (!td || !td->Init(shared_from_this()))
                return false;
            m_threads.push_back(td);
        }
        return true;
    }

    void Start() {
        cout << "strat cs::server" << endl;
        for (auto it = m_threads.begin(); it != m_threads.end(); ++it) {
            (*it)->Start();
        }
        cout << "start cs::server end" << endl;


        for (auto it = m_threads.begin(); it != m_threads.end(); ++it) {
            (*it)->m_td->join();
        }


    }
private:
    Server() : m_port(-1), m_base(nullptr) {
        m_threads.clear();
        cout << "new cs::server" << endl;
    }
public:
    virtual ~Server() {
        m_threads.clear();

        if (m_base) {
            event_base_free(m_base);
            m_base = nullptr;
            m_port = -1;
        }

#if defined(DZLUA_OS_WINDOWS)
        WSACleanup();
#endif
        cout << "delete cs::server" << endl;
    }
private:
    int m_port;
    struct event_base *m_base;
    std::vector<std::shared_ptr<Thread>> m_threads;
};

} // end namespace cd

} // end namespace dzlua

#endif // end DZLUA_LIBEVENT_TCP_SERVER_H_